#include "host_config.h"
#include "host_buf.h"
#include "papi.h"

#include "bt_ctrl.h"
#include "bt_msg.h"

#include "gatt.h"

#include "manager.h"
#include "hid.h"

#define MAX_HID_CONN				2

#define HID_ATT_MTU						23

#define ATT_HID_UUID				0x1812
#define HID_UUID_PROTOCOL			0x2A4E
#define HID_UUID_REPORT				0x2A4D
#define HID_UUID_REPORT_MAP			0x2A4B
#define HID_UUID_BOOT_KB_INPUT		0x2A22
#define HID_UUID_BOOT_KB_OUTPUT		0x2A32
#define HID_UUID_BOOT_MOUSE_INPUT	0x2A33
#define HID_UUID_INFORMATION		0x2A4A
#define HID_UUID_CONTROL_POINT		0x2A4C

enum 
{
	DISCONNECTED = 0,
	CONNECTING,
	SERVICE_DISCOVERY,
	HID_CHAR_DISCOVERY,
	DIS_CHAR_DISCOVERY,
	BS_CHAR_DISCOVERY,
	HID_READ_REPORT_MAP,
	HID_FIND_CHAR_DES,
	HID_SET_INPUT_CHAR_CONFIGURE,
	HID_CONNECTED,
};

enum 
{
	BOOT_MODE = 0,
	REPORT_MODE,
};

typedef struct
{
	t_bdaddr address;
	u_int16	aclHandle;
	u_int8 protocolMode;
	u_int8 serviceNum;
	u_int8 state;
	u_int8 bootPrior;
	void *tid;
	gatt_prime_service_t *pServiceList;
}hidp_core_t;

static hidp_core_t g_hidpCore;

static int hidp_init()
{
	pMemset(&g_hidpCore, 0, sizeof(g_hidpCore));

	return 0;
}

static int hidp_discovery_all_char()
{
	int i = 0;
	u_int16 uuid;
	gatt_client_req_t req;
	gatt_prime_service_t *pService;

	if(g_hidpCore.state == HID_CHAR_DISCOVERY)
	{
		uuid = ATT_HID_UUID;
	}
	else if(g_hidpCore.state == DIS_CHAR_DISCOVERY)
	{
		uuid = ATT_UUID_DEVICEINFO;
	}
	else if(g_hidpCore.state == BS_CHAR_DISCOVERY)
	{
		uuid = ATT_UUID_BATTERY;
	}

	//find service
	pService = g_hidpCore.pServiceList;
	for(; i<g_hidpCore.serviceNum; i++)
	{
		if(pService[i].uuid == uuid)
			break;
	}

	if(i >= g_hidpCore.serviceNum)
	{
		
	}
	else
	{
		req.reqCode = GATT_CHAR_REQ|GATT_CHAR_ALL;
		req.startHandle = pService[i].startHandle;
		req.endHandle = pService[i].endHandle;
		req.module = RDABT_GATT_HIDP;
		g_hidpCore.tid = &pService[i];
#if GATT_CLIENT_SUPPORT == 1
		gatt_client_req(&req);
#endif
	}

	return 0;
}

int hidp_find_all_char_descriptor(u_int16 ServiceUUID)
{
	int i = 0;
	u_int16 handle;
	gatt_chara_t *pChar; 
	gatt_prime_service_t *pService;

	for(; i<g_hidpCore.serviceNum; i++)
	{
		if(g_hidpCore.pServiceList[i].uuid == ATT_HID_UUID)
			break;
	}
	pService = &g_hidpCore.pServiceList[i];

	pChar = pService->pCharaList;
	for(i=0; i<pService->charNum; i++)
	{
		if(pChar[i].descNum == 0)
		{
			if( i == pService->charNum-1 )
				handle = pService->endHandle;
			else
				handle = pChar[i+1].handle-1;

			if(handle - pChar[i].valueHandle > 1)
				break;
			else
				pChar[i].descNum = -1;
		}
	}
	
	if(i<pService->charNum)
	{
		gatt_client_req_t req;
		
		req.reqCode = GATT_DISCOVER_DESCRIPTOR_REQ;
		req.startHandle = pChar[i].valueHandle+1;
		if( i == pService->charNum-1 )
		{
			req.endHandle = pService->endHandle;
		}
		else
		{
			req.endHandle = pChar[i+1].handle-1;
		}
		g_hidpCore.tid = &pChar[i];
		req.module = RDABT_GATT_HIDP;
#if GATT_CLIENT_SUPPORT == 1
		return gatt_client_req(&req);
#else
		return 0;
#endif
	}
	else
	{
		//all the characteristic descriptors have been gotten of this service  
		return 1;
	}
}

int hidp_set_client_config(u_int16 serviceUUID, u_int16 charUUID, u_int16 descUUID, u_int16 value)
{
	u_int8 temp[2];
	gatt_client_req_t req;
	int i = 0, j=0;
	gatt_prime_service_t *pService;
	gatt_chara_desc_t* pDesc;
	gatt_chara_t *pChar;

	//find hid service
	for(; i<g_hidpCore.serviceNum; i++)
	{
		if(g_hidpCore.pServiceList[i].uuid == serviceUUID)
			break;
	}
	pService = &g_hidpCore.pServiceList[i];

	pChar = pService->pCharaList;
	for(i=0; i<pService->charNum; i++)
	{
		if(pChar[i].uuid == charUUID)
		{
			pDesc = pChar[i].pDescList;
			for(j=0; j<pChar[i].descNum; j++)
			{
				if(pDesc[j].uuid == descUUID)
					break;
			}
			if(j <= pChar[i].descNum)
				break;
		}
	}
	
	if(i >= pService->charNum)
		return 1;

	BT_WRITE_LE_UINT16(temp, value);
	req.reqCode = GATT_CHAR_DESCRIPTOR_REQ|GATT_CHAR_DES_WRITE_S;
	req.startHandle = pDesc[j].handle;
	req.attValue = temp;
	req.attValueLen = 2;
	req.module = RDABT_GATT_HIDP;
#if GATT_CLIENT_SUPPORT == 1
	return gatt_client_req(&req);
#else
	return 0;
#endif
}

void hidp_decide_protocol_mode()
{
	gatt_client_req_t req;
	int i = 0;
	gatt_prime_service_t *pService;
	gatt_chara_t *pChar;

	//find hid service
	for(; i<g_hidpCore.serviceNum; i++)
	{
		if(g_hidpCore.pServiceList[i].uuid == ATT_HID_UUID)
			break;
	}
	pService = &g_hidpCore.pServiceList[i];

	if(g_hidpCore.bootPrior)
	{
		//find the protocol characteristic
		pChar = pService->pCharaList;
		for(i=0; i<pService->charNum; i++)
		{
			if(pChar[i].uuid == HID_UUID_PROTOCOL)
				break;
		}

		if(i < pService->charNum)
		{
			u_int8 mode = BOOT_MODE;
			g_hidpCore.protocolMode = BOOT_MODE;
			g_hidpCore.state = HID_CONNECTED;
			
			req.reqCode = GATT_CHAR_VALUE_WRITE_REQ|GATT_CHAR_WWP;
			req.startHandle = pChar[i].valueHandle;
			req.attValue = &mode;
			req.attValueLen = 1;
			req.module = RDABT_GATT_HIDP;
#if GATT_CLIENT_SUPPORT == 1
			gatt_client_req(&req);

#endif
			//notify upper layer everything is ready now.

			return ;
		}	
	}

	pChar = pService->pCharaList;
	for(i=0; i<pService->charNum; i++)
	{
		if(pChar[i].uuid == HID_UUID_REPORT_MAP)
			break;
	}
	
	if(i>=pService->charNum)
	{
		//error, notify upper layer that connection failed 
		return;
	}

	//report mode only
	g_hidpCore.protocolMode = REPORT_MODE;
	g_hidpCore.state = HID_READ_REPORT_MAP;

	req.reqCode = GATT_CHAR_VALUE_READ_REQ|GATT_CHAR_VR; 
	req.startHandle = pChar[i].valueHandle;
	req.module = RDABT_GATT_HIDP;
	g_hidpCore.tid = &pChar[i];
#if GATT_CLIENT_SUPPORT == 1
	gatt_client_req(&req);
#endif
}


APIDECL1 t_api APIDECL2 hidp_connect(t_bdaddr address, u_int8 bootPrior)
{
	u_int16 psm = 0; //0; //0x1f; //ATT_PSM
#if GATT_CLIENT_SUPPORT == 1
	gatt_register_module(address, RDABT_GATT_HIDP);

	gatt_connect(RDABT_GATT_HIDP, psm);
#endif
	g_hidpCore.state = CONNECTING;
	g_hidpCore.bootPrior = bootPrior;

	return 0;
}


t_api rdabt_gatt_hidp_layer_ctrl(u_int8 flags)
{
	return 0;
}

t_api rdabt_gatt_hidp_msg_dispatch(rdabt_msg_t *message)
{
	switch(message->msg_id)
	{
	case GATT_CONNECT_CNF:
		{
			gatt_connect_cnf_t *msg = (gatt_connect_cnf_t*)message->data;
			if(msg->result == 0)
			{
				gatt_client_req_t req;
				req.module = RDABT_GATT_HIDP;
				req.reqCode = GATT_PRIMARY_SERVICE_REQ|GATT_PS_ALL;
#if GATT_CLIENT_SUPPORT == 1
				gatt_client_req(&req);
#endif
				g_hidpCore.state = SERVICE_DISCOVERY;
			}
			else
			{
				t_hid_connect conn;
				conn.result = msg->result;
				RDABT_Send_Message(GATT_HIDP_CONNECT_CNF, RDABT_GATT_HIDP, RDABT_ADP, sizeof(conn), &conn);
			}
		}
		break;
	case GATT_CLIENT_REQ_RSP:
		{
			gatt_client_req_rsp_t *pMsg = (gatt_client_req_rsp_t*)message->data;

			if( (pMsg->reqCode&0xff00) == GATT_PRIMARY_SERVICE_REQ)
			{
				if(pMsg->payLoadLen > 0)
				{
					gatt_prime_service_t *pService;
					unsigned char *ptemp = (unsigned char*)pMsg->rspPayLoad;
					unsigned char valueLen = *ptemp;
					int length = 0, i = 0, hid = 0;
					ptemp ++;
					pMsg->payLoadLen -= 1;

					pService = (gatt_prime_service_t*)rdabt_malloc((pMsg->payLoadLen/valueLen)*sizeof(gatt_prime_service_t));

					while(length < pMsg->payLoadLen)
					{		
						pService[i].startHandle = BT_READ_LE_UINT16(ptemp); //ptemp[0]|ptemp[1]<<8; 
						pService[i].endHandle = BT_READ_LE_UINT16(ptemp+2); //ptemp[2]|ptemp[3]<<8;
						pService[i].uuid = BT_READ_LE_UINT16(ptemp+4); //ptemp[4]|ptemp[5]<<8;
						if(pService[i].uuid == ATT_HID_UUID && hid == 0)
							hid = i;
						length += valueLen;
						ptemp += valueLen;
						i++;

					}
					g_hidpCore.pServiceList = pService;
					g_hidpCore.serviceNum = i;
					
					g_hidpCore.state = HID_CHAR_DISCOVERY;
					hidp_discovery_all_char();
				}
				else
				{
					//notify upper layer connect failed
				}
			}
			else if((pMsg->reqCode&0xff00) == GATT_CHAR_REQ)
			{
				if(pMsg->payLoadLen > 0)
				{
					gatt_prime_service_t *pService;
					gatt_chara_t *pCharList;
					unsigned char *ptemp = (unsigned char*)pMsg->rspPayLoad;
					unsigned char valueLen = *ptemp;
					int length = 0, i = 0, protocol = -1;
					ptemp ++;
					pMsg->payLoadLen -= 1;

					pCharList = (gatt_chara_t*)rdabt_malloc((pMsg->payLoadLen/valueLen)*sizeof(gatt_chara_t));

					while(length < pMsg->payLoadLen)
					{
						pCharList[i].handle = ptemp[1]<<8|ptemp[0];
						pCharList[i].properties = ptemp[2];
						pCharList[i].valueHandle = ptemp[4]<<8|ptemp[3];
						pCharList[i].uuid = ptemp[6]<<8|ptemp[5];

						length += valueLen;
						ptemp += valueLen;
						i++;
					}

					pService = (gatt_prime_service_t*)g_hidpCore.tid;
					pService->pCharaList = pCharList;
					pService->charNum = i;

					if(g_hidpCore.state < BS_CHAR_DISCOVERY)
					{
						g_hidpCore.state ++;
						hidp_discovery_all_char();
					}
					else
					{
						hidp_decide_protocol_mode();
					}
				}
				else
				{
					//notify upper layer connect failed
				}
			}
			else if((pMsg->reqCode&0xff00) == GATT_CHAR_VALUE_READ_REQ)
			{
				gatt_chara_t *pChar = (gatt_chara_t*)g_hidpCore.tid;

				if(g_hidpCore.state == HID_READ_REPORT_MAP)
				{
					if(pMsg->payLoadLen > 0)
					{
						pChar->value = (u_int8*)rdabt_malloc(pMsg->payLoadLen);
						pMemcpy(pChar->value, pMsg->rspPayLoad, pMsg->payLoadLen);
						g_hidpCore.state = HID_FIND_CHAR_DES;
						
						hidp_find_all_char_descriptor(ATT_HID_UUID);

					}
				}
				else
				{
					
				}
			}
			else if((pMsg->reqCode&0xff00) == GATT_DISCOVER_DESCRIPTOR_REQ)
			{
				int i = 0;
				int fmt = 0;
				int length = 1;
				u_int8 *ptemp = pMsg->rspPayLoad;
				gatt_chara_t *pChar = (gatt_chara_t*)g_hidpCore.tid;
				gatt_chara_desc_t *pDesc;
				if(pMsg->payLoadLen > 0)
				{
					fmt = *pMsg->rspPayLoad;
					fmt = fmt==1?4:18;
					ptemp = pMsg->rspPayLoad+1;
					pDesc = (gatt_chara_desc_t*)rdabt_malloc((pMsg->payLoadLen/fmt)*sizeof(gatt_chara_desc_t)); 
					
					while(length < pMsg->payLoadLen)
					{
						pDesc[i].handle = BT_READ_LE_UINT16(ptemp);
						pDesc[i].uuid = BT_READ_LE_UINT16(ptemp+2);

						length += fmt;
						ptemp += fmt;
						i++;
					}
					pChar->descNum = i;
					pChar->pDescList = pDesc;
				}
				else
				{
					pChar->descNum = -1;
				}
				
				if( hidp_find_all_char_descriptor(ATT_HID_UUID) > 0)
				{
					t_hid_connect conn;
					hidp_set_client_config(ATT_HID_UUID, HID_UUID_REPORT, ATT_UUID_CLIENT, 0x0001);
					g_hidpCore.state = HID_CONNECTED;
					conn.result = 0;
					RDABT_Send_Message(GATT_HIDP_CONNECT_CNF, RDABT_GATT_HIDP, RDABT_ADP, sizeof(conn), &conn);
				}
			}
		}
		break;
	case GATT_DISCONNECT_IND:
		break;
	case GATT_INDICATION:
		break;
	case GATT_NOTIFICATION:
		{
			gatt_server_noti_t *msg = (gatt_server_noti_t*)message->data;
			if(msg->length > 0)
			{
				RDABT_Send_Message(GATT_HIDP_NOTIFICATION, RDABT_GATT_HIDP, RDABT_ADP, sizeof(gatt_server_noti_t), msg);
			}
		}
		break;
	default:
		break;
	}
	return 0;
}